// Copyright (C) 2023 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
// 
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<北京小羊驼科技有限公司>，授权编号：<NT231212144350>，代码指纹：<A.702363430.550>，技术对接人微信：<ID: wxid_wbyootvkdcgj22>】
// 【授权寄送：<收件：苏先生、地址：北京市大兴区北京密码西区6号楼1单元301、电话：18613885610、邮箱：bd@huiyuanxiang-inc.com>】
// 
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
// 
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.eva.framework.dbpool;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;

import com.eva.epc.common.util.CommonUtils;
import com.eva.epc.common.util.EException;
import com.eva.framework.utils.LoggerFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;

/**
 * 本对象是作为绑定对应c3p0连接池的实用类，使用时先实例化之（如果不需指定池对象则默认会绑定到默认的c3p0池）.
 * 
 * <pre>
 * 1)freeConnection方法相当于调用ComboPooledDataSource继承自自PooledDataSource的close方法（
 * 		调用本方法即是把连接放回连接池而非真正物理关闭）；
 * 2)realese方法用于重启绑定的连接池对象；
 * 3)close关闭当前DB对关联的c3p0池实例（如果池存在的话），这个关闭实际上是调用c3p0继承自PooledDataSource类
		的close方法，它将释放c3p0池所对应的所有资源，关闭后该池将无法使用。
 * </pre>
 * <p>
 * 另注：娄库类型设定见字段DB_TYPE，本字段是全局标识，后绪的相关方法会据此判断
 * 得出DB类型并对一些数据库相关的SQL作相应转换.
 * </p>
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 * @see com.eva.framework.dbpool.DBUtils
 * @see com.mchange.v2.c3p0.ComboPooledDataSource
 */
public class DBShell
{
	/** 
	 * 数据库类型.<br>
	 * 本字段是全局标识，后绪的相关方法会据此判断得出DB类型并对一些数据库相关的SQL作相应转换 */
	public static DBType DB_TYPE = DBType.sqlServer;
	
	/** 
	 * 提取实体表数据行时，对于NULL值列数据的替换值。
	 * 当需要判断字段值是否对应数据库定义的NULL时，可以
	 * 简单通过相等比较运算符来判断。*/
	public static final String NULL_OF_RESULTSET = "";
	/** 日期格式"yyyy-MM-dd"，用于Java程序代码中的格式转换（不用于SQl中，因不同的数据库pattern要求是不一样的） */
	public static final SimpleDateFormat dayFormat = new SimpleDateFormat("yyyy-MM-dd");
	/** 到分的日期格式"yyyy-MM-dd HH:mm"，用于Java程序代码中的格式转换（不用于SQl中，因不同的数据库pattern要求是不一样的） */
	public static final SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
	/**
	 * SQLException异常message总是包含数据库管理系统的前缀标志，如"ORA-00001: "、"[Microsoft][OD
	 * BC SQL Server Driver][SQL Server]"等，为了友好显示给最终用户，剔除这些没有任何实际意义的标
	 * 志内容而定义了本变量，用于设定原始错误消息起始多少个字符可以剔除。默认为0，表不剔除这个标识头。
	 * 本方法目前仅用于方法 {@link DBUtils##getErrorMessage(SQLException)}的表现，详情请参见该方法.*/
	public static int dbExceptionIdentStringLen = 0;
	/** 
	 * 在出错时是否需要解析更详细的异常信息，本字段目前仅用来控制方
	 * 法 {@link DBUtils#getErrorMessage(SQLException)}的表现，详情请参见该方法.*/
	public static boolean needParseNextExceptionsForSQLException = true;
	public static final IllegalArgumentException UPDATE_DATA_ILEGEL_EXCEPTION= 
		new IllegalArgumentException("当前更新数据无效，原因可能是数据在你本次操作之前已经被改动，请确认后再试！");
	
	/** 本列表用于存放所有已实例化的c3p0池对象（key值与ComboPooledDataSource对象本身属性无关，是DB对象内约定的
	 * ，仅用于本链表的key） */
	private final static HashMap<String,ComboPooledDataSource> c3p0PooledDataSourceList = new HashMap();
	/** 默认的c3p0连接池存放于c3p0PooledDataSourceList链表的名称（与其对象本身无关），相应地方如
	 * 不指明池名称时会默认使用它 */
	private final static String DEFAULT_C3P0_POOLED_DATA_SOURCE_NAME = "_default_c3p0_ds_"; 
	
	/** 处理prepare statement 中的字符串占位符时，使用的SQL 数据类型。默认为java.sql.Types.VARCHAR */
	private int sqlTypeForString = Types.VARCHAR;
	/** 日期类型字段对应的SQL数据类型。默认为java.sql.Types.TIMESTAMP */
	private int sqlTypeForDate = Types.TIMESTAMP;
	/** 本DB对象对应的c3p0连接池实例（一旦绑定到对应的池实例，则本DB对象中的所有有关数据库操作就都是基于它的） */
	private ComboPooledDataSource currentC3p0PoolInstance;
	/** 当前DB对象对应的c3p0连接池实例名（该名称是DB中约定好的，用于c3p0PooledDataSourceList链进行存放管理，
	 * 与ComboPooledDataSource对象本身的名称属性无关） */
	private String currentC3p0PoolInstanceName;
	
	/**
	 * 默认构管中造方法.
	 * <pre>
	 * 	本构造器首先会在c3p0PooledDataSourceList链表中查找默认名称为DEFAULT_C3P0_POOLED_DATA_SOURCE_NAME
	 * 	的实例，如果找到了则把本DB对象跟它绑定，否则新建一个默认的c3p0池对象放入链表并与本DB对象绑定（注该默认
	 * 	c3p0池对象实例化时按官方文档：它将自动搜索配置文件c3p0.properties并读取它的配置内容（本平台下它默认在程序根目录）.
	 * </pre>
	 */
	public DBShell()
	{
		//首先在池对象链表中查找默认的c3p0实例
		ComboPooledDataSource defaultPool = c3p0PooledDataSourceList.get(DEFAULT_C3P0_POOLED_DATA_SOURCE_NAME);
		if(defaultPool==null)
			//如果默认实例不存在则实例化一个并放入链表中
			defaultPool = putAPoolInstance(DEFAULT_C3P0_POOLED_DATA_SOURCE_NAME,new ComboPooledDataSource());
		//将本DB对象与默认的池对象绑定在一起（以后DB对象的所有操作就是依靠 该c3p0实例完成的）
		bindToPoolInstance(DEFAULT_C3P0_POOLED_DATA_SOURCE_NAME,defaultPool);
	}
	/**
	 * 本构造方法表示把一个指定的c3p0实例作为默认池实例放入链表（如果默认实例不存在）并与本DB对象绑定.
	 * 
	 * @param c3p0PooledDataSource 要作为默认c3p0池实例的对象
	 */
	public DBShell(ComboPooledDataSource c3p0PooledDataSource)
	{
		this(DEFAULT_C3P0_POOLED_DATA_SOURCE_NAME,c3p0PooledDataSource);
	}
	/**
	 * 将指定的c3p0池对象以指定的名称放入链表，并与本DB对象进行绑定（如果名称已经存
	 * 在于链表中则本对象会实例化失败）.
	 * 
	 * @param poolName 指定要放入表中的池名称（与ComboPooledDataSource对象本身的名称属性无关，仅对
	 * 		c3p0PooledDataSourceList有意义
	 * @param c3p0PooledDataSource 池实例
	 */
	public DBShell(String poolName,ComboPooledDataSource c3p0PooledDataSource)
	{
		this(poolName,c3p0PooledDataSource,false);
	}
	/**
	 * 将指定的c3p0池对象以指定的名称放入链表，并与本DB对象进行绑定.
	 * 
	 * @param poolName 指定要放入表中的池名称（与ComboPooledDataSource对象本身的名称属性无关，仅对
	 * 		c3p0PooledDataSourceList有意义
	 * @param c3p0PooledDataSource 池实例
	 * @param coverIfExist false表示如果名称存在则不无放入链表，实例化DB会失败，否则直接覆盖原先的同名实例
	 */
	public DBShell(String poolName,ComboPooledDataSource c3p0PooledDataSource,boolean coverIfExist)
	{
		//把实例放入链表存放起来
		putAPoolInstance(poolName,c3p0PooledDataSource,coverIfExist);
		//把当前的DB对象与之绑定
		bindToPoolInstance(poolName,c3p0PooledDataSource);
	}
	/**
	 * 将c3p0实例已指定的名称绑定到当前DB对象，一旦绑定到对应的池实例，
	 * 则本DB对象中的所有有关数据库操作就都是基于它的.
	 * 
	 * @param poolName 用于标识当前DB使用的池实例的名称（与ComboPooledDataSource本身的名称属性无关，仅针对链表
	 * 		c3p0PooledDataSourceList有意义）
	 * @param c3p0PooledDataSource 池实例
	 */
	public void bindToPoolInstance(String poolName,ComboPooledDataSource c3p0PooledDataSource)
	{
		if(CommonUtils.isStringEmpty(poolName,true)||c3p0PooledDataSource==null)
			throw new IllegalArgumentException("参数无效,poolName="
					+poolName+",c3p0PooledDataSource="+c3p0PooledDataSource);
		this.currentC3p0PoolInstance = c3p0PooledDataSource;
		this.currentC3p0PoolInstanceName = poolName;
	}
	
	/**
	 * 本方法的作用仅是将一池实例以指定的名称放入c3p0PooledDataSourceList链表（如果指定名称存在则本次调用会失败）.
	 * @param poolName 用于标识当前DB使用的池实例的名称，与ComboPooledDataSource本身的名称属性无关，仅针对链表
	 * 		c3p0PooledDataSourceList有意义
	 * @param c3p0PooledDataSource 池实例
	 * @return 调用成功后返回原c3p0PooledDataSource对象本身，无特殊作用
	 */
	public static ComboPooledDataSource putAPoolInstance(String poolName,ComboPooledDataSource c3p0PooledDataSource)
	{
		return putAPoolInstance(poolName,c3p0PooledDataSource,false);
	}
	/**
	 * 本方法的作用仅是将一池实例以指定的名称放入c3p0PooledDataSourceList链表.
	 * @param poolName 用于标识当前DB使用的池实例的名称，与ComboPooledDataSource本身的名称属性无关，仅针对链表
	 * 		c3p0PooledDataSourceList有意义
	 * @param c3p0PooledDataSource  池实例
	 * @param coverIfExist false表示如果名称存在则不无放入链表，实例化DB会失败，否则直接覆盖原先的同名实例
	 * @return 调用成功后返回原c3p0PooledDataSource对象本身，无特殊作用
	 */
	public static ComboPooledDataSource putAPoolInstance(String poolName
			,ComboPooledDataSource c3p0PooledDataSource,boolean coverIfExist)
	{
		if(CommonUtils.isStringEmpty(poolName,true))
			throw new IllegalArgumentException("连接池名称不可为空，新连接池实例不允许加入到连接池链.");
		if(coverIfExist&&c3p0PooledDataSourceList.containsKey(poolName))
			throw new IllegalArgumentException("连接池"+poolName+"已经存在，不允许覆盖原先的实例.");
		else
		{
			c3p0PooledDataSourceList.put(poolName, c3p0PooledDataSource);
			return c3p0PooledDataSource;
		}
	}
	
	/**
	 * 获取当前DB对象关联的连接池名称
	 * @return String 连接池名，与ComboPooledDataSource本身的名称属性无关，仅针对链表
	 * 		c3p0PooledDataSourceList有意义
	 */
	public String getBindPoolName()
	{
		return this.currentC3p0PoolInstanceName;
	}

	/**
	 * <p>
	 * <b>预编译语句查询，并返回查询结果</b>.<br>
	 * 获得给定查询语句的数据（使用预编译语句的方式，形如：select f1,f2,f3 from t where key=? and oth=?），
	 * 并以Vector返回。<br><br>
	 * 
	 * <b>提示：</b>本方法在正常执行的情况下将保证无条件返回一个2D向量实例（即使
	 * 在结果集为空的情况，也会保证返回的是空的2D向量实例），详情参见方法 {@link #parseToVector(ResultSet)} .
	 * </p>
	 * 
	 * @param sql 预编译查询语句
	 * @param paramValues 预编译语句中对应参数的值(每一项都不能为null)
	 * @return 以2D向量组织查询结果
	 * @exception 查询执行过程中的任何异常都将被抛出
	 */
	public Vector<Vector> queryData(String sql, Object[] paramValues) throws Exception
	{
		if(CommonUtils.isStringEmpty(sql,true))
			throw new IllegalArgumentException("sql语句是空的，参数无效！");
		
		//声明返回数据容器变量
		Vector<Vector> returnValue = null;
		Connection con = getConnection();
		PreparedStatement pstmt = null;
		try
		{
			LoggerFactory.getLog().debug("queryData{SQL: ".concat(sql)
					.concat(" , VALUES: ").concat(Arrays.toString(paramValues).concat("}")));
			pstmt = con.prepareStatement(sql);
			this.setParameters(pstmt,paramValues);
			returnValue = parseToVector(pstmt.executeQuery());
		}
		catch(SQLException sqle)
		{
			LoggerFactory.getLog().warn("Exception thrown when execute [" + sql + ']');
			throw DBUtils.getErrorMessage(sqle);
		}
		finally
		{
			try{
				if(pstmt!=null)
					pstmt.close();
			}
			catch(Throwable e){}
			this.closeConnection(con);
		}
		returnValue.trimToSize();

		return returnValue;
	}

	/**
	 * <p>
	 * <b>普通SQL语句执行查询，并返回查询结果</b>.<br>
	 * 获得给定查询语句的数据（不是使用预编译语句，形如：select f1,f2,f3 from t where key='abc' and oth='cdf'）
	 * ，并以Vector返回。<br><br>
	 * 
	 * <b>提示：</b>本方法在正常执行的情况下将保证无条件返回一个2D向量实例（即使
	 * 在结果集为空的情况，也会保证返回的是空的2D向量实例），详情参见方法 {@link #parseToVector(ResultSet)} .
	 * </p>
	 * 
	 * @param sql 查询语句
	 * @return 以2D向量组织查询结果
	 * @exception 查询执行过程中的任何异常都将被抛出
	 */
	public Vector<Vector> queryData(String sql) throws Exception
	{
		//声明返回数据容器变量
		Vector returnValue = null;
		if(CommonUtils.isStringEmpty(sql,true))
			throw new IllegalArgumentException("sql语句是空的，参数无效！");
		Connection con = getConnection();
		Statement st = null;
		try
		{
			LoggerFactory.getLog().debug("queryData{SQL: ".concat(sql).concat("}"));
			st = con.createStatement();
			returnValue = parseToVector(st.executeQuery(sql));
		}
		catch(SQLException sqle)
		{
			LoggerFactory.getLog().warn("异常发生在执行语句  [" + sql + ']');
			throw DBUtils.getErrorMessage(sqle);
		}
		finally
		{
			try{
				if(st!=null)
					st.close();
			}
			catch(Throwable e){}
			this.closeConnection(con);
		}
		return returnValue;
	}
	
	/**
	 * <p>
	 * 解析数据库查询结果集以便织成2D向量并返回之。<br>
	 * 
	 * <b>提示：</b>本方法在正常执行的情况下将保证无条件返回一个2D向量实例（即使
	 * 在结果集为空的情况，也会保证返回的是空的2D向量实例）.
	 * </p>
	 * 
	 * @param resultSet 要解析的目标结果集
	 * @return 返回解析并组织完成的2D向量
	 * @throws SQLException
	 */
	private Vector<Vector> parseToVector(ResultSet resultSet) throws SQLException
	{
		//结果集元数据
		ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
		//结果集总列数
		int columnCount = resultSetMetaData.getColumnCount();
		//该字段是否是时间戳类型
		boolean[] isDatetime = new boolean[columnCount + 1];
		for (int i = 1; i <= columnCount; i++)
			isDatetime[i] = resultSetMetaData.getColumnType(i) == sqlTypeForDate;
		
		//用于存放解析得到的数据的2d向量
		Vector<Vector> results = new Vector<Vector>();
		//字段值（它可能是任意数据类型）
		Object fieldValue;
		while(resultSet.next())
		{
			Vector row = new Vector(columnCount);
			//结果集的游标是从1作为起始索引的（不是0，详见JDBC API）
			for(int i = 1;i <= columnCount; i++)
			{
				if (isDatetime[i])
				{
					//额外处理日期类型
					Timestamp st = resultSet.getTimestamp(i);
					if (st != null)
					{
						SimpleDateFormat format;
						//假定秒及和毫秒部分对我们没有任何意义
						if (st.getHours() == 0 && st.getMinutes() == 0)
							format = DBShell.dayFormat;
						else
							format = DBShell.timeFormat;
						fieldValue = format.format(st);
					}
					else
						fieldValue = null;
				}
				//这对于SQLserver2005来说这些字段里存放的就是比特数组
				else if(resultSetMetaData.getColumnType(i)==Types.LONGVARBINARY
						||resultSetMetaData.getColumnType(i)==Types.VARBINARY
						||resultSetMetaData.getColumnType(i)==Types.BINARY)
					fieldValue = resultSet.getBytes(i);//这个字段是SQL Server 2005的varbinary或相似列
				else
					fieldValue = resultSet.getString(i);// getObject不行吗？不行，比如整数返回的是BigDecimal，处理起来就麻烦了，如需特殊处理的列就在上一个esle if里加吧
//				row.add(fieldValue==null?NULL:fieldValue.trim());
				row.add(fieldValue==null?NULL_OF_RESULTSET:(fieldValue instanceof String
						?((String)fieldValue).trim():fieldValue));
			}
			results.add(row);
		}
//		results.trimToSize();
		return results;
	}

	/**
	 * 为预编译语句中的参数设置值
	 * @param paramValues 参数的实际值目前支持的java对象类型有
	 * <ol>
	 * <li>Boolean,Integer,Long,Float,Double,java.util.Date,java.util.Calendar,String,byte[]</li>
	 * </ol>
	 */
	private void setParameters(PreparedStatement pstmt, Object paramValues[])
		throws SQLException
	{
		if (paramValues!=null)
		{
			Object curr;
			int j;
			for (int i = 0; i < paramValues.length; i++)
			{
				curr = paramValues[i];
				j = i+1;
				if(curr == null || "".equals(curr))
					pstmt.setNull(j, Types.VARCHAR);
				else if (curr instanceof String)
					pstmt.setObject(j, curr, sqlTypeForString);
				else if(curr instanceof Integer)
					pstmt.setInt(j, ((Integer)curr).intValue());
				else if(curr instanceof java.util.Date)
					pstmt.setTimestamp(j, new java.sql.Timestamp(((java.util.Date)curr).getTime()));
				else if(curr instanceof java.util.Calendar)
					pstmt.setTimestamp(j, new java.sql.Timestamp((((java.util.Calendar)curr).getTime()).getTime()) );
				else if(curr instanceof Long)
					pstmt.setLong(j, ((Long)curr).longValue());
				else
					pstmt.setObject(j, curr);
			}
		}
	}
	
	/**
	 * <p><b>单个预编译语句执行更新.</b></p>
	 * 使用预编译方式执行关系表的更新，本方法适用于单条SQL语句的情况，省得新建一个Vector。
	 * @param sql 预编译更新语句
	 * @param paramValues 预编译语句中对应参数的值。目前支持的java对象类型有
	 * <ol>
	 * <li>Boolean,Integer,Long,Float,Double,java.util.Date,java.util.Calendar,String,byte[]</li>
	 * </ol>
	 * @param rollbackIfZero 如果有一条语句未影响关系表的数据行，确定该事务是否提交
	 * @return 该语句的执行所影响关系表的数据行数目是否大于零
	 */
	public boolean update(String sql, Object[] paramValues
			, boolean rollbackIfZero) throws Exception
	{
		Vector sqls=new Vector(1);
		if(sql!=null)
			sqls.add(sql);
		Object values[][]=new Object[1][ paramValues==null?0:paramValues.length];
		for (int i = 0; i < values[0].length; i++)
			values[0][i] = paramValues[i];
		return this.update(sqls, values, rollbackIfZero);
	}
	
	/**
	 * <p><b>多个预编译语句执行更新（该指语句实际是没有需要填充的参数的，即paramValues=空Vector，它们形如：
	 * "update someTable where a='123' 或不是像这样 update someTable where a=?"，本方法主要是提供
	 * 一个方便地批量执行这类语句的接口）.</b></p>
	 * 
	 * 使用预编译方式执行关系表的更新.
	 * @param sqls 预编译更新语句集
	 * @param paramValues 每条预编译语句参数值数组构成的向量, 即<br>
	 *    <code>paramValues.get(i) instanceof Vector == true</code>，<br>
	 *    paramValues.get(i)[j] 就是对应prepareSql中第j个?的值。
	 * @param rollbackIfZero 如果有一条语句未影响关系表的数据行，确定该事务是否提交
	 * @return 该语句的执行所影响关系表的数据行数目是否大于零
	 * @see #update(List, List, boolean)
	 */
	public boolean update(List sqls, boolean rollbackIfZero) throws Exception
	{
		return update(sqls, new Vector(), rollbackIfZero);
	}
	/**
	 * <p><b>多个预编译语句执行更新.</b></p>
	 * 使用预编译方式执行关系表的更新.
	 * 
	 * @param sqls 预编译更新语句集
	 * @param paramValues 每条预编译语句参数值数组构成的向量, 即<br>
	 *    <code>paramValues.get(i) instanceof Vector == true</code>，<br>
	 *    paramValues.get(i)[j] 就是对应prepareSql中第j个?的值。
	 * @param rollbackIfZero 如果有一条语句未影响关系表的数据行，确定该事务是否提交
	 * @return 该语句的执行所影响关系表的数据行数目是否大于零
	 * @see #update(List, Object[][], boolean)
	 */
	public boolean update(List sqls, List paramValues, boolean rollbackIfZero) throws Exception
	{
		return update(sqls
			, (Object[][])paramValues.toArray(new Object[paramValues.size()][0])
			, rollbackIfZero);
	}
	/**
	 * <p><b>多个预编译语句执行更新.</b>
	 * 使用预编译方式执行关系表的更新
	 * </p>
	 * 
	 * @param sqls 预编译更新语句集
	 * @param paramValues 预编译语句中对应参数的值
	 * @param rollbackIfZero 如果有一条语句未影响关系表的数据行，确定该事务是否提交
	 * @return 该语句的执行所影响关系表的数据行数目是否大于零
	 * @throws IllegalArgumentException 如果rollbackIfZero==true且该SQL执行时影响行数是0则抛出本异常
	 * @throws Exception 如果在执行SQL的任一环节中出现其它异常则抛出本异常
	 */
	public boolean update(List sqls, Object[][] paramValues, boolean rollbackIfZero) throws Exception
	{
		if(sqls == null)
			throw new IllegalArgumentException("sqls语句集合是空的，参数无效！");
		Connection con=getConnection();
		PreparedStatement pstmt = null;
		int i = 0;
		try
		{
			con.setAutoCommit(false);
			boolean effected = true;//判断更新语句是否影响了数据库表中的行
			for(int n = sqls.size(); i < n; i++)
			{
				pstmt = con.prepareStatement((String)sqls.get(i));
				//预编译语句的参数不为空才需要填充value（为空的表示该update语句里没有额外的?号即没有
				//额外需要填值的参数，直接执行即可）
				if(paramValues!=null&&paramValues.length>0)
					this.setParameters(pstmt, paramValues[i]);
				int cnt = pstmt.executeUpdate();
				if(cnt<=0)
				{
					LoggerFactory.getLog().warn("语句  ["+sqls.get(i)+"]执行时，影响行数为0!<rollbackIfZero?"+rollbackIfZero+">");
					effected = false;
					if(rollbackIfZero)
						break;
				}
				pstmt.close();
			}
			if(!effected && rollbackIfZero)
			{
				con.rollback();
				throw UPDATE_DATA_ILEGEL_EXCEPTION;
			}
			else
				con.commit();
			return effected;
		}
		catch(SQLException ex)
		{
			String valuesForLog = null;
			if(paramValues!=null&&paramValues.length>0)
			{
				// 为了更强壮的代码：防止可能的数组越界
				try
				{
					valuesForLog = " , VALUES: ["+(Arrays.toString(paramValues[i]))+"]";
				}
				catch (Exception e)
				{
					LoggerFactory.getLog().warn("异常发生在valuesForLog!", e);
				}
			}
			else
				valuesForLog = "";
			LoggerFactory.getLog().warn("异常发生在执行SQL: [" + sqls.get(i) + "]"+valuesForLog);
			
			EException err=DBUtils.getErrorMessage(ex);
			EException retErr = err;
			try{
				con.rollback();
			}
			catch(SQLException se)
			{
				EException err2 = DBUtils.getErrorMessage(se);
				retErr = new EException(err.getShortMessage().concat("\r\n").concat(err2.getShortMessage())
						,err.getMessage().concat("\r\n").concat(err2.getMessage()),err.getCause());
			}
			throw retErr;
		}
		finally
		{
			try{
				if(pstmt != null)
					pstmt.close();
				con.setAutoCommit(true);
			}
			catch(Throwable t){
				LoggerFactory.getLog().warn(t.getMessage(), t);
			}
			finally{
				this.closeConnection(con);
			}
		}
	}

	/**
	 * 按默认连接名获取连接
	 */
	public Connection getConnection() throws Exception
	{
		Connection conn=currentC3p0PoolInstance.getConnection();//this.getPoolName());
		if(conn==null)
			throw new IllegalStateException("在本DB对象中，不能获得关联的池 " 
					+ this.getBindPoolName()+" 中的连接，获取数据库连接失败！");
		checkConnection(conn);
		return conn;
	}
	
	/**
	 * 按默认连接名释放连接（实际上是把物理连接对象放入池中，由c3p0连接池来决定如何处理它）.
	 */
	public void closeConnection(Connection con) throws Exception
	{
			if(con!=null)
			{
				checkConnection(con);
//				pm.freeConnection(this.getPoolName(),con);
				con.close();//如果是关闭从连接池中获得的连接，则此关闭并没有从物理上关闭，仅放回了连接池而已
			}
	}
	/** 
	 * 关闭当前DB对关联的c3p0池实例（如果池存在的话），这个关闭实际上是调用c3p0继承自PooledDataSource类
	 * 的close方法，它将释放c3p0池所对应的所有资源，关闭后该池将无法使用 */
	public void close()
	{
		if(currentC3p0PoolInstance!=null)
			//关闭所有跟c3p0相关的东西
			currentC3p0PoolInstance.close();
	}
	/**
	 * 本方法的作用相当于重启当前DB对象关联的currentC3p0PoolInstance实例，默认是软重启，
	 * 即仅关闭所有的checkinconnection,重新初始化池.
	 * @throws Exception
	 */
	public void release()  throws Exception
	{
		release(false);
	}
	/**
	 * 本方法的作用相当于重启当前DB对象关联的currentC3p0PoolInstance实例.
	 * 
	 * @param hardRelease true 表示数据源硬重启（包括数据源对象本身也全部是新对象）
	 * ，否则是数据源软重启（即仅关闭所有的checkinconnection,重新初始化池）
	 */
	public void release(boolean hardRelease) throws Exception
	{
//		this.pm.release();
		if(hardRelease)
			//数据源硬重启（包括数据源本身全部是新的）
			currentC3p0PoolInstance.hardReset();
		else
			//数据源软重启（关闭所有checkinconnection,重新初始化池）
			currentC3p0PoolInstance.softReset(currentC3p0PoolInstance.getUser(), currentC3p0PoolInstance.getPassword());
	}

	/**
	 * 检测该连接是否为事务自动提交方式，如不是则自动设为true。
	 * @param connection
	 * @see Connection#setAutoCommit(boolean)
	 */
	private void checkConnection(Connection connection)
	{
		try{
			if(!connection.getAutoCommit())
				connection.setAutoCommit(true);
		}
		catch (SQLException ex){}
	}
	
	public int getSqlTypeForString()
	{
		return sqlTypeForString;
	}
	/**
	 * 覆盖默认的用于处理prepare statement 中的字符串占位符时所使用的SQL数据类型：java.sql.Types.VARCHAR
	 * @param sqlTypeForString 处理prepare statement 中的字符串占位符时所使用的SQL数据类型
	 */
	public void setSqlTypeForString(int sqlTypeForString)
	{
		this.sqlTypeForString = sqlTypeForString;
	}
	public int getSqlTypeForDate()
	{
		return sqlTypeForDate;
	}
	/**
	 * 覆盖缺省的日期类型字段对应的SQL数据类型：java.sql.Types.TIMESTAMP
	 * @param sqlTypeForDate 日期类型字段对应的SQL数据类型
	 */
	public void setSqlTypeForDate(int sqlTypeForDate)
	{
		this.sqlTypeForDate = sqlTypeForDate;
	}
	
	/**
	 * 获得单一行中的数列（即单行多列）(在有数据的情况下假定只有一行,本方法支持多列,多个表的级联
	 * ,和复杂的where语句).
	 * 
	 * @param field 列名(支持多列),如: "yhm,mm,extra" - 不可为空
	 * @param table 表名(支持多表级联),如："sys_user a join xxx_table b on a.xxx=b.xxx" - 可为空
	 * @param where 条件(支持复杂条件),如：" a.xxx='' and b.xxx='bbb' and ......."  - 可为空
	 * @return
	 */
	public String[] querySingleRowItems(String fieldStr,String tableStr,String whereStr) throws Exception
	{
		if(CommonUtils.isStringEmpty(fieldStr))
			throw new IllegalArgumentException("fieldStr是空的，参数无效！");
		return querySingleRowItems(DBUtils.formatGetComboItemsSql(tableStr,fieldStr,whereStr,null));
	}
	/**
	 * 获得单一行中的数列（即单行多列）(在有数据的情况下假定只有一行,本方法支持多列,多个表的级联
	 * ,和复杂的where语句).
	 * 
	 * @param sql 完整的SQL语句
	 * @return
	 */
	public String[] querySingleRowItems(String sql) throws Exception
	{
		if(CommonUtils.isStringEmpty(sql))
			throw new IllegalArgumentException("sql是空的，参数无效！");
		Vector vec=this.queryData(sql);
		String[] ret=null;
		if(vec.size()>0)
		{
			//只取第一行(如果有多行)
			Vector r=(Vector)vec.get(0);
			ret=new String[r.size()];
			
			//遍历所有列
			for(int i=0;i<r.size();i++)
				ret[i]=(String)r.get(i);
		}
		return ret;
	}
	
	/**
	 * 获得单行单个字段值(如获取的不是单个字段且返回值不只一行则默认取第一行第一列).
	 * @param fieldStr
	 * @param tableStr
	 * @param whereStr
	 * @return
	 * @see #getSingleRowItem(String fieldStr,String tableStr,String whereStr);
	 */
	public String querySingleItem(String fieldStr,String tableStr,String whereStr) throws Exception
	{
		if(CommonUtils.isStringEmpty(fieldStr))
			throw new IllegalArgumentException("fieldStr是空的，参数无效！");
		return querySingleItem(DBUtils.formatGetComboItemsSql(tableStr,fieldStr,whereStr,null));
	}
	/**
	 * 获得单行单个字段值(如获取的不是单个字段且返回值不只一行则默认取第一行第一列).
	 * 
	 * @param sql 完整的SQL语句
	 * @return
	 * @see #getSingleRowItem(String fieldStr,String tableStr,String whereStr);
	 */
	public String querySingleItem(String sql) throws Exception
	{
		String[] item=querySingleRowItems(sql);
		if(item!=null&&item.length>0)
			return item[0];
		else
			return null;
	}
	
	/**
	 * 当前数据类型是否是MySQL.
	 * 
	 * @return
	 * @since 20170722
	 */
	public static boolean isMySQL()
	{
		return DB_TYPE == DBType.mySql;
	}
	
	/**
	 * 当前数据类型是否是SQLSever.
	 * 
	 * @return
	 * @since 20170722
	 */
	public static boolean isSQLServer()
	{
		return DB_TYPE == DBType.sqlServer;
	}
	
	/**
	 * 当前数据类型是否是Oracle.
	 * 
	 * @return
	 * @since 20170722
	 */
	public static boolean isOracle()
	{
		return DB_TYPE == DBType.oracle;
	}
	
	/**
	 * 描述数据库类型的枚举类型.
	 * 
	 * @author js, 2011-02-16
	 * @version 1.0
	 */
	public enum DBType
	{
		oracle,
		sqlServer,
		db2,
		mySql,
		sybase,
		sqlLite,
		postgreSQL,
		msAccess,
		interBase;
	}
}
